﻿// Decompiled with JetBrains decompiler
// Type: Netick.Unity.NetworkSandbox
// Assembly: Netick.Unity, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: E128C7FD-39ED-4452-86AC-6EC748457030
// Assembly location: D:\WorkSpaceDemo\UPM\NetickForUnity\Netick\Runtime\Netick.Unity.dll
// XML documentation location: D:\WorkSpaceDemo\UPM\NetickForUnity\Netick\Runtime\Netick.Unity.xml

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.SceneManagement;

#nullable disable
namespace Netick.Unity;

/// <summary>
/// NetworkSandbox represents a networked game instance in Netick. It encapsulates the entire simulation state for that instance.
/// </summary>
public class NetworkSandbox : MonoBehaviour, IGameEngine
{
  internal SimulationClock _timer;
  internal LocalRigidbodyFixer LocalRigidbodyFixer;
  internal NetworkObject SandboxNetworkObject;
  internal List<NetworkObject> GlobalPrefabs;
  internal bool IsMainSandbox = true;
  internal PhysicsType PhysicsType = PhysicsType.Physics3D;
  internal static System.Type LagCompStep;
  internal NetworkSceneManager NetworkSceneManager;
  internal NetworkSceneHandler NetworkSceneHandler;
  internal NetworkScene MainScene;
  internal HashSet<Scene> Scenes = new HashSet<Scene>(16 /*0x10*/);
  private bool _waitedForOneFrame;
  internal int MainSceneLoadIdCounter;
  internal bool HasLoadScene;
  internal int SceneLoadIdCounter;
  internal SwitchedToScene SwitchedToScene = SwitchedToScene.Invalid;
  internal List<NetickBehaviour> CachedNetickBehaviourList = new List<NetickBehaviour>(64 /*0x40*/);
  internal List<NetworkBehaviour> CachedNetworkBehaviourList = new List<NetworkBehaviour>(64 /*0x40*/);
  private List<GameObject> _cachedList = new List<GameObject>(64 /*0x40*/);
  private List<Component> _cachedList2 = new List<Component>(64 /*0x40*/);
  private List<NetickBaseBehaviour> _cachedList3 = new List<NetickBaseBehaviour>(64 /*0x40*/);
  private Dictionary<Component, bool> _renderComponents = new Dictionary<Component, bool>(1024 /*0x0400*/);
  internal bool _isFolded;
  private bool _isVisible = true;
  internal Dictionary<int, NetworkObject> AllObjects;
  internal Dictionary<ScenePendingObjectKey, Entity> PendingSceneObjects = new Dictionary<ScenePendingObjectKey, Entity>(512 /*0x0200*/);
  internal Dictionary<int, NetworkObjectPool> IdToPrefabPool = new Dictionary<int, NetworkObjectPool>(512 /*0x0200*/);
  internal Dictionary<GameObject, NetworkObjectPool> PrefabToPrefabPool = new Dictionary<GameObject, NetworkObjectPool>(512 /*0x0200*/);

  /// <summary>Use this to associate an object with this sandbox.</summary>
  public object UserObject { get; set; }

  /// <summary>Name of this sandbox.</summary>
  public string Name { get; private set; }

  /// <summary>The NetickEngine instance attached to this sandbox.</summary>
  public NetickEngine Engine { get; private set; }

  /// <summary>The mode this sandbox was started in.</summary>
  public NetickStartMode StartMode => this.Engine.StartMode;

  /// <summary>
  /// Returns true if this <see cref="T:Netick.Unity.NetworkSandbox" /> has been started.
  /// </summary>
  public bool IsRunning => this.Engine != null && this.Engine.IsRunning;

  /// <summary>
  /// Returns true if this <see cref="T:Netick.Unity.NetworkSandbox" /> is the server.
  /// </summary>
  public bool IsServer { get; private set; }

  /// <summary>
  /// Returns true if this <see cref="T:Netick.Unity.NetworkSandbox" /> is a client.
  /// </summary>
  public bool IsClient => !this.IsServer;

  /// <summary>
  /// Returns true if this <see cref="T:Netick.Unity.NetworkSandbox" /> is a host.
  /// </summary>
  public bool IsHost => this.Engine.StartMode == NetickStartMode.Host;

  /// <summary>
  /// Returns true if this <see cref="T:Netick.Unity.NetworkSandbox" /> is a player, which is the case when running as a Client, Host, or in Single-Player mode.
  /// </summary>
  public bool IsPlayer { get; internal set; }

  /// <summary>Configuration data for Netick.</summary>
  public NetickConfig Config { get; internal set; }

  /// <summary>
  /// The <see cref="T:UnityEngine.PhysicsScene2D" /> associated with this <see cref="T:Netick.Unity.NetworkSandbox" />. Always use this to perform physics queries inside the sandbox instead of <see cref="T:UnityEngine.Physics2D" />.
  /// </summary>
  public PhysicsScene2D Physics2D { get; internal set; }

  /// <summary>
  /// The <see cref="T:UnityEngine.PhysicsScene" /> associated with this <see cref="T:Netick.Unity.NetworkSandbox" />. Always use this to perform physics queries inside the sandbox instead of <see cref="T:UnityEngine.Physics" />.
  /// </summary>
  public PhysicsScene Physics { get; internal set; }

  /// <summary>
  /// The <see cref="T:UnityEngine.SceneManagement.Scene" /> associated with this <see cref="T:Netick.Unity.NetworkSandbox" />.
  /// </summary>
  public Scene Scene { get; internal set; }

  /// <summary>
  /// Use this to enable/disable Physics Prediction in the client at runtime.
  /// </summary>
  public bool PhysicsPrediction { get; set; }

  /// <summary>
  /// <i><b>[Client Only]</b></i> The <see cref="!:IPEndPoint" /> of the server you are connected to. Returns null if you are not connected to any server, or if you are the server.
  /// </summary>
  public IEndPoint ServerEndPoint => this.Engine.ServerEndPoint;

  /// <summary>
  /// <i><b>[Client Only]</b></i> Returns true if this client is currently connected to a server.
  /// </summary>
  public bool IsConnected => this.Engine.IsConnected;

  /// <summary>The underlying transport used for low-level networking.</summary>
  public NetworkTransport Transport => this.Engine.Transport;

  /// <summary>
  /// A list containing the ids of all connected players currently. This list is networked and synced to all clients.
  /// </summary>
  public IReadOnlyList<NetworkPlayerId> Players => this.Engine.Players;

  /// <summary>The server the client is connected to.</summary>
  public NetworkConnection ConnectedServer
  {
    get
    {
      return !this.Engine.IsConnected ? (NetworkConnection) null : (NetworkConnection) this.Engine.Client.ConnectedServer;
    }
  }

  /// <summary>
  /// A dictionary containing all simulated/registered network objects [<see cref="T:Netick.Unity.NetworkObject" />] currently.
  /// </summary>
  public IReadOnlyDictionary<int, NetworkObject> Objects
  {
    get => (IReadOnlyDictionary<int, NetworkObject>) this.AllObjects;
  }

  /// <summary>
  /// Use this to manage interest groups and area of interest (AoI) layers.
  /// </summary>
  public InterestManagement InterestManagement => this.Engine.InterestManagement;

  /// <summary>The local player of this sandbox instance.</summary>
  public Netick.NetworkPlayer LocalPlayer => (Netick.NetworkPlayer) this.Engine.LocalPeer;

  /// <summary>
  /// The Local (predicted) Interpolation instance used for interpolating local/predicted objects. Includes useful interpolation data, such as the current interpolation alpha, the ticks being interpolated, and the local interpolation time (in seconds).
  /// </summary>
  public LocalInterpolation LocalInterpolation => this.Engine.LocalInterpolation;

  /// <summary>
  /// The Remote Interpolation instance used for interpolating remote objects on the client. Includes useful interpolation data, such as the current interpolation alpha, the ticks being interpolated, the remote interpolation time (in seconds), and the remote interpolation delay (in seconds).
  /// <para>Note: this is only valid in the client.</para>
  /// </summary>
  public RemoteInterpolation RemoteInterpolation => this.Engine.RemoteInterpolation;

  /// <summary>
  /// The simulation clock used for managing time in the network simulation.
  /// </summary>
  public SimulationClock Timer => this._timer;

  public float LocalAlpha => this.LocalInterpolation.Alpha;

  /// <summary>
  /// Time period between network simulation steps. Same as <see cref="P:UnityEngine.Time.fixedDeltaTime" />
  /// </summary>
  public float FixedDeltaTime => this._timer.FixedDelta;

  /// <summary>
  /// Time period between frames. Same as <see cref="P:UnityEngine.Time.deltaTime" />
  /// </summary>
  public float DeltaTime => Time.deltaTime;

  /// <summary>
  /// Scaled time period between network simulation steps. This is the same as <see cref="P:UnityEngine.Time.fixedDeltaTime" />, but multiplied by <see cref="P:UnityEngine.Time.timeScale" />.
  /// </summary>
  public float ScaledFixedDeltaTime => this.Engine.Timer.FixedDelta * Time.timeScale;

  /// <summary>
  /// Current (local/predicted) network time in seconds. Same as <see cref="P:Netick.LocalInterpolation.Time" />.
  /// </summary>
  public float NetworkTime => this.LocalInterpolation.Time;

  /// <summary>
  /// Current simulation tick.
  /// <para>On the server, <b>it's always going forward/increasing.</b></para>
  /// <para>On the client, <b>during resimulations it returns the current re-simulated tick. </b> To check for resimulations, use <see cref="P:Netick.Unity.NetworkSandbox.IsResimulating" />.</para>
  /// </summary>
  public Tick Tick => this.Engine.Tick;

  /// <summary>
  /// Last received tick on the client. On the server, it returns the same value as <see cref="!:Sandbox.Tick." />
  /// </summary>
  public Tick AuthoritativeTick => this.Engine.AuthoritativeTick;

  /// <summary>
  /// Last predicted tick.
  /// <para>On the client, <b>it always returns the predicted tick of the client, regardless of whether it's a resimulation or not.</b></para>
  /// <para>On the server, <b>it returns the same value as Sandbox.Tick</b></para>
  /// </summary>
  public Tick PredictedTick => this.Engine.PredictedTick;

  /// <summary>
  /// Returns true if we are currently re-simulating a previous input/tick of the past. On the server, it always returns false since <b>only the clients resimulate</b>.
  /// </summary>
  public bool IsResimulating => this.Engine.IsResimulating;

  /// <summary>
  /// <i><b>[Client Only]</b></i> The number of re-simulations that have been performed in this tick. This is only valid on the client (and during the re-simulation stage), and it returns 0 on the server.
  /// </summary>
  public int ResimulationCount
  {
    get => !this.IsServer ? this.Engine.ClientSimulation.ResimulationCount : 0;
  }

  /// <summary>
  /// <i><b>[Client Only]</b></i> Returns the current re-simulation step we are currently performing, if we are in the re-simulation stage. This is only valid on the client, and it returns 0 on the server.
  /// </summary>
  public int ResimulationStep => !this.IsServer ? this.Engine.ClientSimulation.ResimulationStep : 0;

  /// <summary>
  /// Returns true if this NetworkFixedUpdate call is the first one executed in this frame. This excludes re-simulations, and it only refers to new/forward ticks.
  /// </summary>
  public bool IsFirstNetworkFixedUpdateCall => this.Engine.IsFirstNetworkFixedUpdateCall;

  /// <summary>
  /// Returns true if this NetworkFixedUpdate call is the last one executed in this frame. This excludes re-simulations, and it only refers to new/forward ticks.
  /// </summary>
  public bool IsLastNetworkFixedUpdateCall => this.Engine.IsLastNetworkFixedUpdateCall;

  /// <summary>Incoming data in kilobytes per second (KBps).</summary>
  public float InKBps => this.Engine.InKBps;

  /// <summary>Outgoing data in kilobytes per second (KBps).</summary>
  public float OutKBps => this.Engine.OutKBps;

  /// <summary>
  /// <i><b>[Client Only]</b></i> Estimation of incoming packets that are being lost. Value ranges between 0.0 and 1.0, with 1.0 meaning all packets are being lost.
  /// </summary>
  public float InPacketLoss => this.Engine.InPacketLoss;

  /// <summary>
  /// <i><b>[Client Only]</b></i> Estimation of outgoing packets that are being lost. Value ranges between 0.0 and 1.0, with 1.0 meaning all packets are being lost.
  /// </summary>
  public float OutPacketLoss => this.Engine.OutPacketLoss;

  /// <summary>
  /// <i><b>[Client Only]</b></i> Remote Interpolation delay in seconds.
  /// </summary>
  public float InterpolationDelay => this.Engine.InterpolationDelay;

  /// <summary>
  /// <i><b>[Client Only]</b></i> The round-trip time (RTT) of the client in seconds.
  /// </summary>
  public double RTT => this.Engine.RTT;

  /// <summary>Used to track network metrics and statistics.</summary>
  public Monitor Monitor => this.Engine.Monitor;

  /// <summary>
  /// Called after all NetworkFixedUpdate methods have been executed.
  /// <para>Note: it's called with every resimulation on the client.</para>
  /// </summary>
  public event Action PostNetworkFixedUpdate
  {
    add => this.Engine.PostNetworkFixedUpdate += value;
    remove => this.Engine.PostNetworkFixedUpdate -= value;
  }

  /// <summary>
  /// Called before any NetworkFixedUpdate method have been executed.
  /// <para>Note: it's called with every resimulation on the client.</para>
  /// </summary>
  public event Action PreNetworkFixedUpdate
  {
    add => this.Engine.PreNetworkFixedUpdate += value;
    remove => this.Engine.PreNetworkFixedUpdate -= value;
  }

  /// <summary>
  /// Called after all NetworkUpdate methods have been executed.
  /// </summary>
  public event Action PostNetworkUpdate
  {
    add => this.Engine.PostNetworkUpdate += value;
    remove => this.Engine.PostNetworkUpdate -= value;
  }

  /// <summary>
  /// Called before any NetworkUpdate method have been executed.
  /// </summary>
  public event Action PreNetworkUpdate
  {
    add => this.Engine.PreNetworkUpdate += value;
    remove => this.Engine.PreNetworkUpdate -= value;
  }

  /// <summary>
  /// Called after all NetworkRender methods have been executed.
  /// </summary>
  public event Action PostNetworkRender
  {
    add => this.Engine.PostNetworkRender += value;
    remove => this.Engine.PostNetworkRender -= value;
  }

  /// <summary>
  /// Called before any NetworkRender method have been executed.
  /// </summary>
  public event Action PreNetworkRender
  {
    add => this.Engine.PreNetworkRender += value;
    remove => this.Engine.PreNetworkRender -= value;
  }

  /// <summary>
  /// Use this to subscribe/unsubscribe to/from Netick events.
  /// </summary>
  public NetworkCallbacks Events { get; private set; }

  internal int MainSceneIndex { get; set; } = -1;

  internal unsafe bool ServerHasLoadedScene
  {
    get
    {
      return ((ConnectionMeta*) this.Engine.Client.ConnectedServer.LocalUserData)->HasLoadedScene == 1;
    }
  }

  internal bool IsClientNotReady
  {
    get
    {
      if (this.IsClient && !this.IsConnected)
        return true;
      return this.IsClient && !this.ServerHasLoadedScene;
    }
  }

  internal bool IsPhysicsSteppedByNetick => this.PhysicsType != 0;

  internal bool InvokeRenderInHeadless
  {
    get => !Application.isBatchMode || this.Config.InvokeRenderInHeadless;
  }

  /// <summary>
  /// <i><b>[Client Only]</b></i> Connects to a remote server.
  /// <para>Note: if you are already connected to a server, and you want to connect to a different one, you have to disconnect before calling this. Otherwise this method does nothing.</para>
  /// <para>Note: connectionData array length should not be more than 300 if you are using Netick Transport.</para>
  /// </summary>
  /// <param name="ip">IP address of the server.</param>
  /// <param name="port">Port of the server.</param>
  /// <param name="connectionData">RawValue sent with the connection request. Could be used to decide whether or not to allow the connection.</param>
  public void Connect(int port, string ip, byte[] connectionData = null, int connectionDataLength = 0)
  {
    this.Engine.Connect(port, ip, connectionData, connectionDataLength);
  }

  /// <summary>
  /// <i><b>[Client Only]</b></i> Disconnects this client from the server.
  /// </summary>
  public void DisconnectFromServer() => this.Engine.DisconnectFromServer();

  /// <summary>
  /// <i><b>[Server Only]</b></i> Disconnects a client from the server.
  /// </summary>
  /// <param name="client">The client to be disconnected.</param>
  public void Kick(NetworkPlayerId client)
  {
    this.Engine.Kick(this.GetPlayerById(client) as ServerConnection);
  }

  public void Kick(ServerConnection client) => this.Engine.Kick(client);

  public void Kick(Netick.NetworkPlayer client) => this.Engine.Kick(client as ServerConnection);

  /// <summary>
  /// <i><b>[Server Only]</b></i> Creates a match. Only functional if the low-level transport has support for matchmaking.
  /// </summary>
  /// <param name="name">Name of the match.</param>
  public void CreateMatch(string name) => this.Engine.LocalPlayer.Transport.HostMatch(name);

  /// <summary>
  /// Requests an update to the match list. Only functional if the low-level transport has support for matchmaking.
  /// </summary>
  public void RefreshMatchList() => this.Engine.LocalPeer.Transport.UpdateMatchList();

  /// <summary>
  /// <i><b>[Server Only]</b></i> Switches to a different scene. This must be called instead of <see cref="M:UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(System.Int32)" /> or its variations. This should only be called for scenes included in the build of the game.
  /// </summary>
  /// <param name="sceneBuildIndex">SceneBuildIndex of the desired scene.</param>
  public void SwitchScene(string sceneName)
  {
    this.SwitchScene(Network.Instance.GetSceneBuildIndex(sceneName));
  }

  /// <summary>
  /// <i><b>[Server Only]</b></i> Switches to a different scene. This must be called instead of <see cref="M:UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(System.Int32)" /> or its variations. This should only be called for scenes included in the build of the game.
  /// </summary>
  /// <param name="sceneBuildIndex">SceneBuildIndex of the desired scene.</param>
  public void SwitchScene(int sceneBuildIndex)
  {
    if (this.IsClient)
      throw new NotServerException();
    ++this.SceneLoadIdCounter;
    this.MainSceneLoadIdCounter = this.SceneLoadIdCounter;
    this.SwitchedToScene = new SwitchedToScene()
    {
      BuildIndex = sceneBuildIndex,
      IsValid = true,
      Counter = this.MainSceneLoadIdCounter
    };
  }

  /// <summary>
  /// <i><b>[Server Only]</b></i>  Loads a scene asynchronously. This should only be called for scenes included in the build of the game.
  /// </summary>
  public void LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode)
  {
    this.LoadSceneAsync(Network.Instance.GetSceneBuildIndex(sceneName), loadSceneMode);
  }

  public void LoadSceneAsync(string sceneName, LoadSceneParameters loadSceneParameters)
  {
    this.LoadSceneAsync(Network.Instance.GetSceneBuildIndex(sceneName), loadSceneParameters);
  }

  public void LoadSceneAsync(int buildIndex, LoadSceneMode loadSceneMode)
  {
    if (loadSceneMode == LoadSceneMode.Single)
      this.SwitchScene(buildIndex);
    else
      this.NetworkSceneManager.LoadAdditiveScene(buildIndex, LocalPhysicsMode.None);
  }

  public void LoadSceneAsync(int buildIndex, LoadSceneParameters loadSceneParameters)
  {
    if (loadSceneParameters.loadSceneMode == LoadSceneMode.Single)
      this.SwitchScene(buildIndex);
    else
      this.NetworkSceneManager.LoadAdditiveScene(buildIndex, loadSceneParameters.localPhysicsMode);
  }

  /// <summary>
  /// <i><b>[Server Only]</b></i>  Unloads a scene asynchronously.
  /// </summary>
  public void UnloadSceneAsync(string sceneName)
  {
    this.UnloadSceneAsync(SceneManager.GetSceneByName(sceneName));
  }

  public void UnloadSceneAsync(int buildIndex)
  {
    this.NetworkSceneManager.UnloadAdditiveScene(buildIndex);
  }

  public void UnloadSceneAsync(Scene scene)
  {
    if (!scene.IsValid())
      return;
    this.NetworkSceneManager.UnloadAdditiveScene(scene);
  }

  public void LoadCustomSceneAsync(int customIndex, LoadSceneParameters loadSceneParameters)
  {
    this.LoadSceneAsync(SceneManager.sceneCountInBuildSettings + customIndex, loadSceneParameters);
  }

  public void UnloadCustomSceneAsync(int customIndex)
  {
    this.UnloadSceneAsync(SceneManager.sceneCountInBuildSettings + customIndex);
  }

  internal void Init(
    NetickConfig netickConfig,
    NetworkTransportProvider networkTransportProvider,
    NetickStartMode startMode,
    bool createNewScene,
    string name,
    int port,
    ReflectionData reflectionData)
  {
    Time.fixedDeltaTime = 1f / netickConfig.TickRate;
    this.Config = netickConfig;
    this.IsMainSandbox = !createNewScene;
    this.IsServer = startMode != NetickStartMode.Client;
    this.IsPlayer = startMode == NetickStartMode.SinglePlayer || startMode == NetickStartMode.Host || startMode == NetickStartMode.Client;
    this.Name = this.IsServer ? "Server " + name : "Client " + name;
    this.Engine = new NetickEngine();
    this.AllObjects = new Dictionary<int, NetworkObject>(this.Config.MaxObjects);
    this.PhysicsPrediction = this.Config.PhysicsPrediction;
    this.LocalRigidbodyFixer = new LocalRigidbodyFixer(this);
    this.Events = new NetworkCallbacks(this);
    this.MainScene = new NetworkScene(this, true, -1, false, new Scene(), -1);
    this.GlobalPrefabs = netickConfig.Prefabs;
    NetickConfigData config;
    this.Config.GetNetickConfigData(out config);
    this.SandboxNetworkObject = this.GetComponent<NetworkObject>();
    if ((UnityEngine.Object) this.SandboxNetworkObject == (UnityEngine.Object) null)
      this.SandboxNetworkObject = this.gameObject.AddComponent<NetworkObject>();
    foreach (NetworkEventsListener componentsInChild in this.GetComponentsInChildren<NetworkEventsListener>())
      this.Events.Subscribe(componentsInChild, this.SandboxNetworkObject);
    if (NetworkSandbox.LagCompStep != (System.Type) null && this.Config.EnableLagCompensation)
      this.gameObject.AddComponent(NetworkSandbox.LagCompStep);
    if ((UnityEngine.Object) this.GetComponent<PhysicsSimulationStep>() == (UnityEngine.Object) null && this.Config.PhysicsType != PhysicsType.None)
      this.gameObject.AddComponent<PhysicsSimulationStep>();
    if ((UnityEngine.Object) this.GetComponent<GridRenderer>() == (UnityEngine.Object) null && this.Config.RenderWorldGrid)
      this.gameObject.AddComponent<GridRenderer>();
    if ((UnityEngine.Object) this.GetComponent<NetworkSceneManager>() == (UnityEngine.Object) null)
      this.gameObject.AddComponent<NetworkSceneManager>();
    if ((UnityEngine.Object) this.GetComponent<NetworkSceneHandler>() == (UnityEngine.Object) null)
      this.gameObject.AddComponent<DefaultSceneHandler>();
    this.NetworkSceneHandler = this.GetComponent<NetworkSceneHandler>();
    this.NetworkSceneManager = this.GetComponent<NetworkSceneManager>();
    bool isArm32Bit = !Environment.Is64BitProcess && CultureInfo.InvariantCulture.CompareInfo.IndexOf(SystemInfo.processorType, "ARM", CompareOptions.IgnoreCase) >= 0;
    int num = Application.platform == RuntimePlatform.WebGLPlayer ? 0 : SystemInfo.processorCount - 1;
    int numberOfThreads = this.Config.EnableMultithreading ? num : 0;
    NetworkTransport transport = startMode != NetickStartMode.SinglePlayer ? networkTransportProvider.MakeTransportInstance() : (NetworkTransport) null;
    this.Engine.Start((object) this, this.Name, port, startMode, (IGameEngine) this, config, transport, reflectionData, (INetickLogger) new UnityLogger(), (IMemoryAllocator) new UnityAllocator(), (MismatchChecker) new NetickUnityMismatchChecker(this, (NetickEngine) null), numberOfThreads, this.Config.AggressivePreAllocation, this.Config.FastSerialization, isArm32Bit, this.Config.EnableProfiling, this.Config.EnableLogging);
    this.Engine.PreRollback += new Action(this.LocalRigidbodyFixer.PreRollback);
    this.Engine.PostResimulation += new Action(this.LocalRigidbodyFixer.PostResimulation);
    this._timer = this.Engine.Timer;
    this.NetworkSceneManager.Init(this.Config.MaxAdditiveScenes);
    this.Engine.OnTickEnd += new Action(this.NetworkSceneManager.Process);
    this.InitNetworkPrefabPools(this.GlobalPrefabs);
    this.InitSandboxObject();
    if (!this.Config.EnableProfiling)
      return;
    this.InitProfilers();
  }

  internal void UpdateInternal()
  {
    if (!this._waitedForOneFrame)
    {
      for (int index = 0; index < StaticUnitySceneManager.InitialScenes.Count; ++index)
      {
        Scene freeScene;
        StaticUnitySceneManager.TryConsumeFreeScene(StaticUnitySceneManager.InitialScenes[index], out freeScene);
        this.Scenes.Add(freeScene);
        if (index == 0)
          this.MainScene.OnSceneLoaded(freeScene, freeScene.buildIndex, this.MainSceneLoadIdCounter, true, true, false);
      }
      this.InvokeNetworkAwakeOnSandboxObject();
      this.PreloadNetworkPrefabPools(this.GlobalPrefabs);
      this._waitedForOneFrame = true;
    }
    else
    {
      this.Engine.Update(Time.deltaTime, Time.timeScale, !this.IsClientNotReady);
      if (!this.InvokeRenderInHeadless || this.Config.RenderInvokeOrder != NetworkRenderInvokeOrder.Update)
        return;
      this.Engine.Render();
      this.LocalRigidbodyFixer?.Interpolate(this.Engine.Timer.Alpha);
    }
  }

  internal void LateUpdateInternal()
  {
    if (!this.InvokeRenderInHeadless || this.Config.RenderInvokeOrder != NetworkRenderInvokeOrder.LateUpdate)
      return;
    this.Engine.Render();
    this.LocalRigidbodyFixer?.Interpolate(this.Engine.Timer.Alpha);
  }

  internal void Shutdown(bool destroyAllNetworkObjects)
  {
    this.Events.InvokeOnShutdown(this);
    if (this.IsHost)
      this.Engine.DisconnectHost();
    this.DestroyNetworkPools();
    if (!this.IsMainSandbox)
    {
      if (this.HasLoadScene && this.Scene.IsValid())
        StaticUnitySceneManager.UnloadScene(this, this.Scene);
    }
    else if (destroyAllNetworkObjects)
    {
      foreach (NetworkObject networkObject in this.FindObjectsOfType<NetworkObject>(false))
      {
        if ((UnityEngine.Object) networkObject != (UnityEngine.Object) null && (UnityEngine.Object) networkObject.gameObject != (UnityEngine.Object) null)
          UnityEngine.Object.Destroy((UnityEngine.Object) networkObject.gameObject);
      }
    }
    if ((UnityEngine.Object) this.gameObject != (UnityEngine.Object) null)
      UnityEngine.Object.Destroy((UnityEngine.Object) this.gameObject);
    this.Engine.Shutdown();
    this.Engine = (NetickEngine) null;
  }

  private void OnDestroy() => this.Engine?.Shutdown();

  /// <summary>
  /// Finds the <see cref="T:Netick.Unity.NetworkSandbox" /> instance associated with the specified <see cref="T:UnityEngine.GameObject" />.
  /// Returns null if networking is not running or if no matching sandbox is found for the object's scene.
  /// </summary>
  /// <param name="go">The <see cref="T:UnityEngine.GameObject" /> to find the sandbox for.</param>
  /// <returns>The <see cref="T:Netick.Unity.NetworkSandbox" /> associated with the object's scene, or null if not found.</returns>
  public static NetworkSandbox FindSandboxOf(MonoBehaviour monoBehaviour)
  {
    return NetworkSandbox.FindSandboxOf(monoBehaviour.gameObject);
  }

  /// <summary>
  /// Finds the <see cref="T:Netick.Unity.NetworkSandbox" /> instance associated with the specified <see cref="T:UnityEngine.GameObject" />.
  /// Returns null if networking is not running or if no matching sandbox is found for the object's scene.
  /// </summary>
  /// <param name="go">The <see cref="T:UnityEngine.GameObject" /> to find the sandbox for.</param>
  /// <returns>The <see cref="T:Netick.Unity.NetworkSandbox" /> associated with the object's scene, or null if not found.</returns>
  public static NetworkSandbox FindSandboxOf(GameObject go)
  {
    if (!Network.IsRunning)
      return (NetworkSandbox) null;
    foreach (NetworkSandbox sandbox in (IEnumerable<NetworkSandbox>) Network.Sandboxes)
    {
      if (sandbox.Scene == go.scene)
        return sandbox;
    }
    return (NetworkSandbox) null;
  }

  internal static void EnsureSingleInstances(Scene scene, NetworkSandbox sandbox)
  {
    if (sandbox.IsMainSandbox)
      return;
    foreach (SingleInstanceEnforcer instanceEnforcer in UnityEngine.Object.FindObjectsOfType<SingleInstanceEnforcer>())
    {
      if (instanceEnforcer.gameObject.scene == scene)
        UnityEngine.Object.Destroy((UnityEngine.Object) instanceEnforcer.gameObject);
    }
  }

  internal static void GetNetworkListeners(Scene source, NetworkSandbox sandbox)
  {
    foreach (NetworkEventsListener networkEventsListener in UnityEngine.Object.FindObjectsOfType<NetworkEventsListener>(sandbox.Config.IncludeInactiveObjects))
    {
      if (networkEventsListener.gameObject.scene == source)
      {
        foreach (NetworkEventsListener component in networkEventsListener.GetComponents<NetworkEventsListener>())
          sandbox.Events.Subscribe(component, (NetworkObject) null);
      }
    }
  }

  public List<GameObject> FindGameObjectsWithTag(string tag, bool activeOnly = true)
  {
    this.Scene.GetRootGameObjects(this._cachedList);
    List<GameObject> objs = new List<GameObject>();
    foreach (GameObject cached in this._cachedList)
    {
      if (cached.tag == tag && (!activeOnly || activeOnly && cached.gameObject.activeInHierarchy))
        objs.Add(cached);
      this.InternalFindObjectsWithTag(tag, cached, objs, activeOnly);
    }
    this._cachedList.Clear();
    return objs;
  }

  public List<GameObject> FindGameObjectsWithTag(
    string tag,
    List<GameObject> results,
    bool activeOnly = true)
  {
    this.Scene.GetRootGameObjects(this._cachedList);
    results.Clear();
    foreach (GameObject cached in this._cachedList)
    {
      if (cached.tag == tag && (!activeOnly || activeOnly && cached.gameObject.activeInHierarchy))
        results.Add(cached);
      this.InternalFindObjectsWithTag(tag, cached, results, activeOnly);
    }
    this._cachedList.Clear();
    return results;
  }

  private void InternalFindObjectsWithTag(
    string tag,
    GameObject obj,
    List<GameObject> objs,
    bool activeOnly)
  {
    for (int index = 0; index < obj.transform.childCount; ++index)
    {
      Transform child = obj.transform.GetChild(index);
      if (child.tag == tag && (!activeOnly || activeOnly && child.gameObject.activeInHierarchy))
        objs.Add(child.gameObject);
      this.InternalFindObjectsWithTag(tag, child.gameObject, objs, activeOnly);
    }
  }

  public GameObject FindGameObjectWithTag(string tag, bool activeOnly = true)
  {
    this.Scene.GetRootGameObjects(this._cachedList);
    GameObject gameObjectWithTag = (GameObject) null;
    foreach (GameObject cached in this._cachedList)
    {
      if (cached.tag == tag)
      {
        gameObjectWithTag = cached;
        break;
      }
      GameObject gameObject = this.InternalFindObjectWithTag(tag, cached, activeOnly);
      if (((!((UnityEngine.Object) gameObject != (UnityEngine.Object) null) ? 0 : (!gameObject.gameObject.activeInHierarchy ? 1 : 0)) & (activeOnly ? 1 : 0)) != 0)
        gameObject = (GameObject) null;
      if ((UnityEngine.Object) gameObject != (UnityEngine.Object) null)
      {
        gameObjectWithTag = cached;
        break;
      }
    }
    this._cachedList.Clear();
    return gameObjectWithTag;
  }

  private GameObject InternalFindObjectWithTag(string tag, GameObject obj, bool activeOnly)
  {
    GameObject objectWithTag = (GameObject) null;
    for (int index = 0; index < obj.transform.childCount; ++index)
    {
      Transform child = obj.transform.GetChild(index);
      if (child.tag == tag)
      {
        objectWithTag = child.gameObject;
        break;
      }
      GameObject gameObject = this.InternalFindObjectWithTag(tag, child.gameObject, activeOnly);
      if (((!((UnityEngine.Object) gameObject != (UnityEngine.Object) null) ? 0 : (!gameObject.gameObject.activeInHierarchy ? 1 : 0)) & (activeOnly ? 1 : 0)) != 0)
        gameObject = (GameObject) null;
      if ((UnityEngine.Object) gameObject != (UnityEngine.Object) null)
      {
        objectWithTag = gameObject;
        break;
      }
    }
    this._cachedList.Clear();
    return objectWithTag;
  }

  public T FindObjectOfType<T>(bool activeOnly = true) where T : class
  {
    T objectOfType = default (T);
    this.Scene.GetRootGameObjects(this._cachedList);
    foreach (GameObject cached in this._cachedList)
    {
      objectOfType = cached.GetComponentInChildren<T>();
      Component component = (object) objectOfType as Component;
      if (((!((UnityEngine.Object) component != (UnityEngine.Object) null) ? 0 : (!component.gameObject.activeInHierarchy ? 1 : 0)) & (activeOnly ? 1 : 0)) != 0)
        objectOfType = default (T);
      if ((object) objectOfType != null)
        break;
    }
    this._cachedList.Clear();
    return objectOfType;
  }

  public List<T> FindObjectsOfType<T>(bool activeOnly = true) where T : class
  {
    List<T> results = new List<T>();
    this.Scene.GetRootGameObjects(this._cachedList);
    foreach (GameObject cached in this._cachedList)
      this.GetInChild<T>(cached, activeOnly, results);
    this._cachedList.Clear();
    return results;
  }

  public void FindObjectsOfType<T>(List<T> results, bool activeOnly = true) where T : class
  {
    this.Scene.GetRootGameObjects(this._cachedList);
    results.Clear();
    foreach (GameObject cached in this._cachedList)
      this.GetInChild<T>(cached, activeOnly, results);
    this._cachedList.Clear();
  }

  private void GetInChild<T>(GameObject obj, bool activeOnly, List<T> results) where T : class
  {
    obj.GetComponents(typeof (T), this._cachedList2);
    foreach (Component component in this._cachedList2)
    {
      if ((!activeOnly || activeOnly && obj.activeInHierarchy) && (object) (component as T) != null)
        results.Add(component as T);
    }
    for (int index = 0; index < obj.transform.childCount; ++index)
      this.GetInChild<T>(obj.transform.GetChild(index).gameObject, activeOnly, results);
    this._cachedList2.Clear();
  }

  internal static void GetBehaviours<T>(
    NetworkSandbox sandbox,
    GameObject obj,
    List<NetickBehaviour> behaviourList,
    List<NetworkEventsListener> netListeners,
    bool includeInactive)
    where T : NetickBehaviour
  {
    obj.GetComponents<NetickBaseBehaviour>(sandbox._cachedList3);
    foreach (NetickBaseBehaviour netickBaseBehaviour in sandbox._cachedList3)
    {
      if (includeInactive || obj.activeInHierarchy)
      {
        if (netickBaseBehaviour is T)
          behaviourList.Add((NetickBehaviour) (netickBaseBehaviour as T));
        else if (netickBaseBehaviour is NetworkEventsListener && netListeners != null)
          netListeners.Add(netickBaseBehaviour as NetworkEventsListener);
      }
    }
    for (int index = 0; index < obj.transform.childCount; ++index)
    {
      Transform child = obj.transform.GetChild(index);
      if (!((UnityEngine.Object) child.GetComponent<NetworkObject>() != (UnityEngine.Object) null))
        NetworkSandbox.GetBehaviours<T>(sandbox, child.gameObject, behaviourList, netListeners, includeInactive);
    }
    sandbox._cachedList3.Clear();
  }

  int IGameEngine.GetConnectionMetaSizeWords() => NetickUtils.GetWordSize(sizeof (ConnectionMeta));

  int IGameEngine.GetEntityMetaSizeWords()
  {
    return NetickUtils.GetWordSize(this.Config.SyncTransformOnSpawn ? sizeof (NetworkObjectMeta) : sizeof (NetworkObjectLightMeta));
  }

  void IGameEngine.OnInput() => this.Events.InvokeOnInput(this);

  void IGameEngine.OnMatchListUpdate(List<Session> sessions)
  {
    this.Events.InvokeOnMatchListUpdate(this, sessions);
  }

  unsafe void IGameEngine.OnReceiveData(
    NetworkConnection sender,
    byte id,
    byte* data,
    int len,
    TransportDeliveryMethod deliveryMethod)
  {
    this.Events.InvokeOnReceiveData(this, sender, id, data, len, deliveryMethod);
  }

  void IGameEngine.OnConnectFailed(ConnectionFailedReason reason)
  {
    this.Events.InvokeOnConnectFailed(this, reason);
  }

  void IGameEngine.OnConnectRequest(NetworkConnectionRequest request)
  {
    this.Events.InvokeOnConnectRequest(this, request);
  }

  void IGameEngine.OnPlayerConnected(Netick.NetworkPlayer player)
  {
    this.Events.InvokeOnPlayerConnected(this, player);
  }

  void IGameEngine.OnPlayerDisconnected(
    Netick.NetworkPlayer player,
    TransportDisconnectReason transportDisconnectReason)
  {
    this.Events.InvokeOnPlayerDisconnected(this, player, transportDisconnectReason);
  }

  void IGameEngine.OnPlayerJoined(NetworkPlayerId player)
  {
    this.Events.InvokeOnPlayerJoined(this, player);
  }

  void IGameEngine.OnPlayerLeft(NetworkPlayerId player)
  {
    this.Events.InvokeOnPlayerLeft(this, player);
  }

  void IGameEngine.OnConnectedToServer()
  {
    this.Events.InvokeOnConnectedToServer(this, (NetworkConnection) this.Engine.Client.ConnectedServer);
  }

  void IGameEngine.OnDisconnectedFromServer(
    TransportDisconnectReason transportDisconnectReason)
  {
  }

  unsafe void IGameEngine.OnPeerConnected(NetworkConnection conn)
  {
    *(ConnectionMeta*) conn.LocalUserData = new ConnectionMeta()
    {
      CurrentSceneIndex = -1,
      SceneOperationsCounter = 0,
      HasLoadedScene = 0
    };
    *(ConnectionMeta*) conn.RemoteUserData = new ConnectionMeta()
    {
      CurrentSceneIndex = -1,
      SceneOperationsCounter = 0,
      HasLoadedScene = 0
    };
    if (!this.Engine.IsServer)
      return;
    this.Events.InvokeOnClientConnected(this, conn);
  }

  void IGameEngine.OnPeerDisconnected(
    NetworkConnection conn,
    TransportDisconnectReason transportDisconnectReason)
  {
    if (this.Engine.IsServer)
    {
      this.Events.InvokeOnClientDisconnected(this, conn, transportDisconnectReason);
    }
    else
    {
      this.Events.InvokeOnDisconnectedFromServer(this, conn, transportDisconnectReason);
      if (this.Engine.AllEntities.ContainsKey(this.SandboxNetworkObject.Entity.RawId))
        this.Engine.DestroyEntity(this.SandboxNetworkObject.Entity, true, false);
      this.HasLoadScene = true;
      this.MainSceneIndex = -1;
    }
  }

  unsafe void IGameEngine.OnBeforeSend(NetworkConnection conn)
  {
    *(ConnectionMeta*) conn.LocalUserData = new ConnectionMeta()
    {
      CurrentSceneIndex = this.MainSceneIndex,
      SceneOperationsCounter = this.MainSceneLoadIdCounter,
      HasLoadedScene = this.HasLoadScene ? 1 : 0
    };
    if (!this.IsServer)
      return;
    ConnectionMeta* remoteUserData = (ConnectionMeta*) conn.RemoteUserData;
    conn.ReplicateEntities = remoteUserData->HasLoadedScene == 1 && remoteUserData->CurrentSceneIndex == this.MainSceneIndex && remoteUserData->SceneOperationsCounter == this.MainSceneLoadIdCounter;
  }

  unsafe void IGameEngine.OnPacketReceived(NetworkConnection source)
  {
    if (!this.Engine.IsClient || !this._waitedForOneFrame || !this.HasLoadScene)
      return;
    ConnectionMeta connectionMeta = *(ConnectionMeta*) source.RemoteUserData;
    if (this.MainSceneIndex == connectionMeta.CurrentSceneIndex && this.MainSceneLoadIdCounter == connectionMeta.SceneOperationsCounter)
      return;
    this.SwitchedToScene = new SwitchedToScene()
    {
      BuildIndex = connectionMeta.CurrentSceneIndex,
      IsValid = true,
      Counter = connectionMeta.SceneOperationsCounter
    };
  }

  void IGameEngine.SyncTransformsToColliders()
  {
    if (this.Config.PhysicsType == PhysicsType.Physics2D)
      UnityEngine.Physics2D.SyncTransforms();
    else
      UnityEngine.Physics.SyncTransforms();
  }

  public LoadSceneParameters GetDefaultLoadSceneParameters()
  {
    return new LoadSceneParameters(Network.StartMode == Netick.Unity.StartMode.MultiplePeers ? LoadSceneMode.Additive : LoadSceneMode.Single, this.IsMainSandbox ? LocalPhysicsMode.None : this.GetDefaultPhysicsMode());
  }

  public LocalPhysicsMode GetDefaultPhysicsMode()
  {
    if (this.Config.PhysicsType == PhysicsType.None)
      return LocalPhysicsMode.None;
    return this.Config.PhysicsType != PhysicsType.Physics2D ? LocalPhysicsMode.Physics3D : LocalPhysicsMode.Physics2D;
  }

  /// <summary>
  /// Returns true if input is enabled on this sandbox. Use this to enable/disable input at runtime.
  /// </summary>
  public bool InputEnabled
  {
    set => this.Engine.IsInputEnabled = value;
    get => this.Engine.IsInputEnabled;
  }

  /// <summary>
  /// Returns true if this sandbox is visible. Use this to enable/disable visibility at runtime.
  /// </summary>
  public bool IsVisible
  {
    set
    {
      foreach (Component key in this._renderComponents.Keys.ToArray<Component>())
      {
        if ((UnityEngine.Object) key == (UnityEngine.Object) null)
          this._renderComponents.Remove(key);
        else
          this.SetViewComponentEnabledState(key, value && this._renderComponents[key]);
      }
      int num1 = this._isVisible ? 1 : 0;
      this._isVisible = value;
      int num2 = value ? 1 : 0;
      if (num1 == num2)
        return;
      this.Events.InvokeOnVisibilityChanged(this);
    }
    get => this._isVisible;
  }

  public void EnableComponent(Component component) => this.SetComponentEnabled(component, true);

  public void DisableComponent(Component component) => this.SetComponentEnabled(component, false);

  public void SetComponentEnabled(Component component, bool enabled)
  {
    if ((UnityEngine.Object) Network.Instance != (UnityEngine.Object) null && Network.StartMode == Netick.Unity.StartMode.MultiplePeers && this.IsViewComponent(component))
    {
      if (!this._renderComponents.ContainsKey(component))
        this._renderComponents.Add(component, enabled);
      else
        this._renderComponents[component] = enabled;
    }
    if (component is Renderer)
      ((Renderer) component).enabled = this.IsVisible & enabled;
    else
      ((Behaviour) component).enabled = this.IsVisible & enabled;
  }

  internal void AttachComponents(List<Component> list)
  {
    for (int index = 0; index < list.Count; ++index)
    {
      Component key = list[index];
      if (this.IsViewComponent(key))
      {
        if (!this._renderComponents.ContainsKey(key))
          this._renderComponents.Add(key, this.GetViewComponentEnabledState(key));
        this.SetViewComponentEnabledState(key, this.IsVisible && this._renderComponents[key]);
      }
    }
  }

  private bool GetViewComponentEnabledState(Component obj)
  {
    return !(obj is Renderer) ? ((Behaviour) obj).enabled : ((Renderer) obj).enabled;
  }

  private void SetViewComponentEnabledState(Component obj, bool state)
  {
    if (obj is Renderer)
      ((Renderer) obj).enabled = state;
    else
      ((Behaviour) obj).enabled = state;
  }

  private bool IsViewComponent(Component obj)
  {
    if (obj.gameObject.scene != this.Scene)
      return false;
    switch (obj)
    {
      case Light _:
      case Renderer _:
      case AudioListener _:
      case AudioSource _:
      case Camera _:
      case Canvas _:
        return !((UnityEngine.Object) obj.GetComponent<SingleInstanceEnforcer>() != (UnityEngine.Object) null);
      default:
        return false;
    }
  }

  public void AttachGameObject(GameObject gameObject)
  {
    if (this.IsPhysicsSteppedByNetick)
      this.LocalRigidbodyFixer.AddRigid(gameObject);
    if (Network.Instance._startMode != Netick.Unity.StartMode.MultiplePeers)
      return;
    gameObject.GetComponentsInChildren<Component>(true, this._cachedList2);
    this.AttachComponents(this._cachedList2);
    this._cachedList2.Clear();
  }

  public void DetachGameObject(GameObject gameObject)
  {
    if (this.IsPhysicsSteppedByNetick)
      this.LocalRigidbodyFixer.Remove(gameObject);
    if (Network.Instance._startMode != Netick.Unity.StartMode.MultiplePeers)
      return;
    gameObject.GetComponentsInChildren<Component>(true, this._cachedList2);
    foreach (Component key in this._cachedList2)
      this._renderComponents.Remove(key);
    this._cachedList2.Clear();
  }

  /// <summary>
  /// Attaches a <see cref="T:Netick.Unity.NetickBehaviour" /> to the simulation.
  /// </summary>
  /// <param name="behaviour"></param>
  public void AttachBehaviour(NetickBehaviour behaviour)
  {
    this.Engine.AttachBehaviour((INetickScript) behaviour);
  }

  /// <summary>
  /// Detaches a <see cref="T:Netick.Unity.NetickBehaviour" /> from the simulation.
  /// </summary>
  /// <param name="behaviour"></param>
  public void DetachBehaviour(NetickBehaviour behaviour)
  {
    this.Engine.DetachBehaviour((INetickScript) behaviour);
  }

  public void AttachNonNetworkedRigidbody(Rigidbody rigidbody)
  {
    this.LocalRigidbodyFixer.AddRigidbody(rigidbody.gameObject);
  }

  public void DetachNonNetworkedRigidbody(Rigidbody rigidbody)
  {
    this.LocalRigidbodyFixer.RemoveRigidbody(rigidbody.gameObject);
  }

  public void AttachNonNetworkedRigidbody(GameObject rigidbodyGameObject)
  {
    this.LocalRigidbodyFixer.AddRigidbody(rigidbodyGameObject);
  }

  public void DetachNonNetworkedRigidbody(GameObject rigidbodyGameObject)
  {
    this.LocalRigidbodyFixer.RemoveRigidbody(rigidbodyGameObject);
  }

  public void RemoveFromLoop(NetworkObject obj)
  {
    this.Engine.Simulation.RemoveFromNetworkLoop(obj.Entity);
  }

  /// <summary>
  /// Instantiates a prefab. This must be called instead of <see cref="M:UnityEngine.Object.Instantiate(UnityEngine.Object)" />.
  /// <para>Note: this must be called only for non-networked prefabs. If you want to instantiate a network prefab, use <see cref="M:Netick.Unity.NetworkSandbox.NetworkInstantiate(UnityEngine.GameObject,UnityEngine.Vector3,UnityEngine.Quaternion,Netick.NetworkPlayer)" /> instead.</para>
  /// </summary>
  public GameObject LocalInstantiate(GameObject prefab, Vector3 position)
  {
    return this.LocalInstantiate(prefab, position, Quaternion.identity);
  }

  public GameObject LocalInstantiate(GameObject prefab)
  {
    return this.LocalInstantiate(prefab, new Vector3());
  }

  public T LocalInstantiate<T>(T prefab, Vector3 position, Quaternion rotation) where T : MonoBehaviour
  {
    return this.LocalInstantiate(prefab.gameObject, position, rotation).GetComponent<T>();
  }

  public T LocalInstantiate<T>(T prefab, Vector3 position) where T : MonoBehaviour
  {
    return this.LocalInstantiate(prefab.gameObject, position, Quaternion.identity).GetComponent<T>();
  }

  public T LocalInstantiate<T>(T prefab) where T : MonoBehaviour
  {
    return this.LocalInstantiate(prefab.gameObject, new Vector3(), Quaternion.identity).GetComponent<T>();
  }

  public GameObject LocalInstantiate(GameObject prefab, Vector3 position, Quaternion rotation)
  {
    GameObject gameObject = UnityEngine.Object.Instantiate<GameObject>(prefab, position, rotation);
    SceneManager.MoveGameObjectToScene(gameObject, this.Scene);
    this.AttachGameObject(gameObject);
    return gameObject;
  }

  /// <summary>
  /// <i><b>[Server Only]</b></i> Instantiates a network prefab.
  /// <para>This must only be called on the server, since only the server can instantiate network prefabs.</para>
  /// <para>Note: make sure the prefab has been registered. </para>
  /// <para>Note: the specified input source will be given to every <see cref="T:Netick.Unity.NetworkObject" /> child of this prefab.</para>
  /// </summary>
  public NetworkObject NetworkInstantiate(
    GameObject prefab,
    Vector3 position,
    Quaternion rotation,
    Netick.NetworkPlayer inputSource = null)
  {
    return this.NetworkInstantiateInternal(prefab, position, rotation, inputSource);
  }

  /// <summary>
  /// <i><b>[Server Only]</b></i> Instantiates a network prefab.
  /// <para>This must only be called on the server, since only the server can instantiate network prefabs.</para>
  /// <para>Note: make sure the prefab has been registered. </para>
  /// <para>Note: the specified input source will be given to every <see cref="T:Netick.Unity.NetworkObject" /> child of this prefab.</para>
  /// </summary>
  public NetworkObject NetworkInstantiate(
    GameObject prefab,
    Vector3 position,
    Quaternion rotation,
    NetworkPlayerId inputSource)
  {
    return this.NetworkInstantiate(prefab, position, rotation, this.GetPlayerById(inputSource));
  }

  /// <summary>
  /// <i><b>[Server Only]</b></i> Instantiates a network prefab.
  /// <para>This must only be called on the server, since only the server can instantiate network prefabs.</para>
  /// <para>Note: make sure the prefab has been registered. </para>
  /// <para>Note: the specified input source will be given to every <see cref="T:Netick.Unity.NetworkObject" /> child of this prefab.</para>
  /// </summary>
  public T NetworkInstantiate<T>(
    T prefab,
    Vector3 position,
    Quaternion rotation,
    Netick.NetworkPlayer inputSource = null)
    where T : MonoBehaviour
  {
    return this.NetworkInstantiate(prefab.gameObject, position, rotation, inputSource).GetComponent<T>();
  }

  /// <summary>
  /// <i><b>[Server Only]</b></i> Instantiates a network prefab.
  /// <para>This must only be called on the server, since only the server can instantiate network prefabs.</para>
  /// <para>Note: make sure the prefab has been registered. </para>
  /// <para>Note: the specified input source will be given to every <see cref="T:Netick.Unity.NetworkObject" /> child of this prefab.</para>
  /// </summary>
  public T NetworkInstantiate<T>(
    T prefab,
    Vector3 position,
    Quaternion rotation,
    NetworkPlayerId inputSource)
    where T : MonoBehaviour
  {
    return this.NetworkInstantiate(prefab.gameObject, position, rotation, inputSource).GetComponent<T>();
  }

  public NetworkObject NetworkInstantiate(
    GameObject prefab,
    Vector3 position,
    Netick.NetworkPlayer inputSource = null)
  {
    return this.NetworkInstantiate(prefab, position, Quaternion.identity, inputSource);
  }

  public NetworkObject NetworkInstantiate(
    GameObject prefab,
    Vector3 position,
    NetworkPlayerId inputSource)
  {
    return this.NetworkInstantiate(prefab, position, Quaternion.identity, this.GetPlayerById(inputSource));
  }

  public T NetworkInstantiate<T>(T prefab, Vector3 position, Netick.NetworkPlayer inputSource = null) where T : MonoBehaviour
  {
    return this.NetworkInstantiate(prefab.gameObject, position, Quaternion.identity, inputSource).GetComponent<T>();
  }

  public T NetworkInstantiate<T>(T prefab, Vector3 position, NetworkPlayerId inputSource) where T : MonoBehaviour
  {
    return this.NetworkInstantiate(prefab.gameObject, position, Quaternion.identity, inputSource).GetComponent<T>();
  }

  public NetworkObject NetworkInstantiate(GameObject prefab, Netick.NetworkPlayer inputSource = null)
  {
    return this.NetworkInstantiate(prefab, new Vector3(), Quaternion.identity, inputSource);
  }

  public NetworkObject NetworkInstantiate(GameObject prefab, NetworkPlayerId inputSource)
  {
    return this.NetworkInstantiate(prefab, new Vector3(), Quaternion.identity, this.GetPlayerById(inputSource));
  }

  public T NetworkInstantiate<T>(T prefab, Netick.NetworkPlayer inputSource = null) where T : MonoBehaviour
  {
    return this.NetworkInstantiate(prefab.gameObject, new Vector3(), Quaternion.identity, inputSource).GetComponent<T>();
  }

  public T NetworkInstantiate<T>(T prefab, NetworkPlayerId inputSource) where T : MonoBehaviour
  {
    return this.NetworkInstantiate(prefab.gameObject, new Vector3(), Quaternion.identity, inputSource).GetComponent<T>();
  }

  /// <summary>
  /// Gets the the associated NetworkObject (player object) with this player.
  /// </summary>
  /// <returns></returns>
  public NetworkObject GetPlayerObject(Netick.NetworkPlayer player)
  {
    return this.GetPlayerObject(player.PlayerId);
  }

  /// <summary>
  /// Gets the the associated script (player object) of type <see cref="!:T" /> with this player.
  /// </summary>
  /// <returns></returns>
  public T GetPlayerObject<T>(Netick.NetworkPlayer player) where T : NetickBehaviour
  {
    return (UnityEngine.Object) this.GetPlayerObject(player.PlayerId) != (UnityEngine.Object) null ? this.GetPlayerObject(player.PlayerId).GetComponent<T>() : default (T);
  }

  /// <summary>
  /// Tries to get the the associated NetworkObject (player object) with this player.
  /// </summary>
  /// <returns></returns>
  public bool TryGetPlayerObject(Netick.NetworkPlayer player, out NetworkObject playerObject)
  {
    return this.TryGetPlayerObject(player.PlayerId, out playerObject);
  }

  /// <summary>
  /// Tries to get the the associated script (player object) of type <see cref="!:T" /> with this player.
  /// </summary>
  /// <returns></returns>
  public bool TryGetPlayerObject<T>(Netick.NetworkPlayer player, out T playerObject) where T : NetickBehaviour
  {
    return this.TryGetPlayerObject<T>(player.PlayerId, out playerObject);
  }

  /// <summary>
  /// Gets the the associated NetworkObject (player object) with this player.
  /// </summary>
  /// <returns></returns>
  public NetworkObject GetPlayerObject(NetworkPlayerId id)
  {
    Entity playerEntity = this.Engine.GetPlayerEntity(id);
    return playerEntity != null ? playerEntity.UserEntity as NetworkObject : (NetworkObject) null;
  }

  /// <summary>
  /// Gets the the associated script (player object) of type <see cref="!:T" /> with this player.
  /// </summary>
  /// <returns></returns>
  public T GetPlayerObject<T>(NetworkPlayerId id) where T : NetickBehaviour
  {
    Entity playerEntity = this.Engine.GetPlayerEntity(id);
    return playerEntity != null ? (playerEntity.UserEntity as NetworkObject).GetComponent<T>() : default (T);
  }

  /// <summary>
  /// Tries to get the the associated NetworkObject (player object) with this player.
  /// </summary>
  /// <returns></returns>
  public bool TryGetPlayerObject(NetworkPlayerId id, out NetworkObject playerObject)
  {
    playerObject = this.GetPlayerObject(id);
    return (UnityEngine.Object) playerObject != (UnityEngine.Object) null;
  }

  /// <summary>
  /// Tries to get the the associated script (player object) of type <see cref="!:T" /> with this player.
  /// </summary>
  /// <returns></returns>
  public bool TryGetPlayerObject<T>(NetworkPlayerId id, out T playerObject) where T : NetickBehaviour
  {
    playerObject = this.GetPlayerObject<T>(id);
    return (UnityEngine.Object) playerObject != (UnityEngine.Object) null;
  }

  /// <summary>
  /// <i><b>[Server Only]</b></i> Sets the the associated NetworkObject (player object) with this player.
  /// </summary>
  /// <returns></returns>
  public void SetPlayerObject(NetworkPlayerId id, NetworkObject obj)
  {
    if (this.IsClient)
      throw new NotServerException();
    this.Engine.SetPlayerEntity(id, (UnityEngine.Object) obj != (UnityEngine.Object) null ? obj.Entity : (Entity) null);
  }

  public void SetPlayerObject(Netick.NetworkPlayer player, NetworkObject obj)
  {
    this.SetPlayerObject(player.PlayerId, obj);
  }

  /// <summary>
  /// Gets the the associated <see cref="T:Netick.Unity.NetworkObject" /> (player object) with the local player.
  /// </summary>
  /// <returns></returns>
  public NetworkObject GetLocalPlayerObject() => this.GetPlayerObject(this.LocalPlayer.PlayerId);

  /// <summary>
  /// Gets the the associated script (player object) of type <see cref="!:T" /> with the local player.
  /// </summary>
  /// <returns></returns>
  public T GetLocalPlayerObject<T>() where T : NetickBehaviour
  {
    return this.GetPlayerObject<T>(this.LocalPlayer.PlayerId);
  }

  /// <summary>
  /// Tries to get the the associated <see cref="T:Netick.Unity.NetworkObject" /> (player object) with the local player.
  /// </summary>
  /// <returns></returns>
  public bool TryGetLocalPlayerObject(out NetworkObject playerObject)
  {
    return this.TryGetPlayerObject(this.LocalPlayer.PlayerId, out playerObject);
  }

  /// <summary>
  /// Tries to get the the associated script (player object) of type <see cref="!:T" /> with the local player.
  /// </summary>
  /// <returns></returns>
  public bool TryGetLocalPlayerObject<T>(out T playerObject) where T : NetickBehaviour
  {
    return this.TryGetPlayerObject<T>(this.LocalPlayer.PlayerId, out playerObject);
  }

  /// <summary>
  /// Retrieves the <see cref="T:Netick.NetworkPlayer" /> associated with the given <see cref="T:Netick.NetworkPlayerId" />.
  /// <para>
  /// On the server, this method can return any player by their ID. On the client, it only returns the local player or the server player.
  /// </para>
  /// </summary>
  public Netick.NetworkPlayer GetPlayerById(NetworkPlayerId playerId)
  {
    return this.Engine.GetPlayerById(playerId);
  }

  /// <summary>
  /// Checks if a player with the specified <see cref="T:Netick.NetworkPlayerId" /> exists in the simulation.
  /// </summary>
  public bool ContainsPlayer(NetworkPlayerId playerId) => this.Engine.ContainsPlayer(playerId);

  /// <summary>
  /// Gets the <see cref="T:Netick.Unity.NetworkObject" /> with the specified id. Returns null in case no object with that id exists.
  /// </summary>
  /// <param name="id"> The id of the <see cref="T:Netick.Unity.NetworkObject" /></param>
  /// <returns></returns>
  public NetworkObject GetObject(int id)
  {
    NetworkObject networkObject;
    return this.TryGetObject(out networkObject, id) ? networkObject : (NetworkObject) null;
  }

  public NetworkObject GetObject(NetworkObjectRef networkObjectRef)
  {
    NetworkObject networkObject;
    return this.TryGetObject(out networkObject, networkObjectRef.ObjectId) ? networkObject : (NetworkObject) null;
  }

  /// <summary>
  /// Tries to get the <see cref="T:Netick.Unity.NetworkObject" /> with the specified id.
  /// </summary>
  /// <param name="id"> The id of the <see cref="T:Netick.Unity.NetworkObject" /></param>
  /// <returns></returns>
  public bool TryGetObject(int id, out NetworkObject obj) => this.TryGetObject(out obj, id);

  public bool TryGetObject(NetworkObjectRef networkObjectRef, out NetworkObject obj)
  {
    return this.TryGetObject(out obj, networkObjectRef.ObjectId);
  }

  private bool TryGetObject(out NetworkObject obj, int id)
  {
    obj = (NetworkObject) null;
    return this.AllObjects.TryGetValue(id, out obj);
  }

  public T GetBehaviour<T>(NetworkBehaviourRef<T> networkBehaviourRef) where T : NetworkBehaviour
  {
    T behaviour;
    return this.TryGetBehaviour<T>(networkBehaviourRef, out behaviour) ? behaviour : default (T);
  }

  /// <summary>
  /// Tries to get the <see cref="T:Netick.Unity.NetworkBehaviour" /> of a <see cref="T:Netick.Unity.NetworkObject" /> with the specified id.
  /// </summary>
  /// <param name="id"> The id of the network object</param>
  /// <returns></returns>
  public bool TryGetBehaviour<T>(int id, out T behaviour) where T : NetworkBehaviour
  {
    return this.TryGetBehaviour<T>(out behaviour, id);
  }

  public bool TryGetBehaviour<T>(NetworkBehaviourRef<T> networkBehaviourRef, out T behaviour) where T : NetworkBehaviour
  {
    return this.TryGetBehaviour<T>(out behaviour, networkBehaviourRef.ObjectId, networkBehaviourRef.BehaviourId);
  }

  private bool TryGetBehaviour<T>(out T behaviour, int id) where T : NetworkBehaviour
  {
    behaviour = default (T);
    NetworkObject networkObject;
    if (this.AllObjects.TryGetValue(id, out networkObject))
      behaviour = networkObject.GetBehaviour<T>();
    return (UnityEngine.Object) behaviour != (UnityEngine.Object) null;
  }

  private bool TryGetBehaviour<T>(out T behaviour, int id, int behId) where T : NetworkBehaviour
  {
    behaviour = default (T);
    NetworkObject networkObject;
    if (this.AllObjects.TryGetValue(id, out networkObject))
      behaviour = networkObject.GetBehaviourById<T>(behId);
    return (UnityEngine.Object) behaviour != (UnityEngine.Object) null;
  }

  /// <summary>
  /// Gets all network behaviours (or Netick behaviours) in the simulation that have the type of <see cref="!:T" />.
  /// </summary>
  public void GetBehavioursOfType<T>(List<T> result) where T : NetickBehaviour
  {
    this.Engine.GetBehavioursOfType<T>(result);
  }

  /// <summary>
  /// Gets all network behaviours (or Netick behaviours) in the simulation that have the type of <see cref="!:T" />.
  /// </summary>
  public List<T> GetBehavioursOfType<T>() where T : NetickBehaviour
  {
    List<T> result = new List<T>(10);
    this.Engine.GetBehavioursOfType<T>(result);
    return result;
  }

  /// <summary>Gets a specific input for the next tick.</summary>
  public T GetInput<T>(int index = 0) where T : unmanaged => this.Engine.GetInput<T>(index);

  /// <summary>Sets a specific input for the next tick.</summary>
  /// <typeparam name="T"></typeparam>
  public void SetInput<T>(T input, int index = 0) where T : unmanaged
  {
    this.Engine.SetInput<T>(input, index);
  }

  /// <summary>Converts ticks to time in seconds.</summary>
  /// <param name="tick"></param>
  /// <returns></returns>
  public float TickToTime(Tick tick) => (float) tick.Value * this.FixedDeltaTime;

  /// <summary>Converts ticks to time in seconds.</summary>
  /// <param name="tick"></param>
  /// <returns></returns>
  public float TickToTime(float tick) => tick * this.FixedDeltaTime;

  /// <summary>Converts time (in seconds) to ticks.</summary>
  /// <returns></returns>
  public Tick TimeToTick(float time)
  {
    return new Tick((int) ((double) time / (double) this.FixedDeltaTime));
  }

  public NetworkTimer StartTimer(float time, bool usePredictedTiming = true)
  {
    return NetworkTimer.StartTimer(time, this.Engine, usePredictedTiming);
  }

  private void InitProfilers()
  {
    this.InitProfiler(this.Monitor.UpdateEntryPoint);
    this.InitProfiler(this.Monitor.SendProfiler);
    this.InitProfiler(this.Monitor.ReceiveProfiler);
    this.InitProfiler(this.Monitor.ProcessStateChangesProfiler);
    this.InitProfiler(this.Monitor.TickProfiler);
    this.InitProfiler(this.Monitor.GameEngineIntoNetcodeProfiler);
    this.InitProfiler(this.Monitor.NetcodeIntoGameEngineProfiler);
    this.InitProfiler(this.Monitor.ResimulateProfiler);
    this.InitProfiler(this.Monitor.UpdateProfiler);
    this.InitProfiler(this.Monitor.FixedUpdateProfiler);
    this.InitProfiler(this.Monitor.RenderProfiler);
  }

  private void InitProfiler(NetickProfiler profiler)
  {
    ProfilerMarker marker = new ProfilerMarker(ProfilerCategory.Scripts, profiler.Name);
    profiler.OnStarted += (Action) (() => marker.Begin());
    profiler.OnEnded += (Action) (() => marker.End());
  }

  public void Log(object message) => Debug.Log((object) $"[{this.Name}] {message}");

  public void LogWarning(object message) => Debug.LogWarning((object) $"[{this.Name}] {message}");

  public void LogError(object message) => Debug.LogError((object) $"[{this.Name}] {message}");

  public void Log(object message, UnityEngine.Object context)
  {
    Debug.Log((object) $"[{this.Name}] {message}", context);
  }

  public void LogWarning(object message, UnityEngine.Object context)
  {
    Debug.LogWarning((object) $"[{this.Name}] {message}", context);
  }

  public void LogError(object message, UnityEngine.Object context)
  {
    Debug.LogError((object) $"[{this.Name}] {message}", context);
  }

  /// ****************************************************** Deprecated ******************************************************
  ///             <summary>
  /// A list containing all connected clients currently.
  /// <para>Note: if you want the clients + the server, use <see cref="P:Netick.Unity.NetworkSandbox.ConnectedPlayers" />.</para>
  /// </summary>
  [Obsolete("This will be removed in the future, please use Players instead.")]
  public IReadOnlyList<ServerConnection> ConnectedClients => this.Engine.ConnectedClients;

  /// <summary>
  /// <para>A list containing all connected clients currently, in addition to the server (when started as a host).</para>
  /// <para>Note: if you only want the clients, use <see cref="P:Netick.Unity.NetworkSandbox.ConnectedClients" />.</para>
  /// </summary>
  [Obsolete("This will be removed in the future, please use Players instead.")]
  public IReadOnlyList<Netick.NetworkPlayer> ConnectedPlayers => this.Engine.ConnectedPlayers;

  /// <summary>
  /// Always always returns null except when called inside the body of an RPC method, it returns the <see cref="T:Netick.NetworkConnection" /> we are executing RPCs from.
  /// </summary>
  [Obsolete("This will be removed in the future, please read the doc article on Rpcs to learn the new replacement.")]
  public NetworkConnection CurrentRpcSource => this.Engine.CurrentRpcSource;

  /// <summary>
  /// Always always returns null except when called inside the body of an RPC method, it returns the <see cref="T:Netick.NetworkPlayer" /> we are executing RPCs from. Works the same as <see cref="P:Netick.Unity.NetworkSandbox.CurrentRpcSource" />, except when the RPC is called by the local player, it returns <see cref="!:Sandbox.LocalPlayer" />.
  /// </summary>
  [Obsolete("This will be removed in the future, please read the doc article on Rpcs to learn the new replacement.")]
  public Netick.NetworkPlayer CurrentRpcCaller => this.Engine.CurrentRpcCaller;

  [Obsolete("This will be removed in the future, please use ResimulationCount instead.")]
  public int Resimulations => !this.IsServer ? this.Engine.ClientSimulation.ResimulationCount : 0;

  [Obsolete("This will be removed in the future, please use LocalInstantiate instead.")]
  public GameObject Instantiate(GameObject prefab, Vector3 position, Quaternion rotation)
  {
    return this.LocalInstantiate(prefab, position, rotation);
  }

  [Obsolete("This will be removed in the future, please use LocalInstantiate instead.")]
  public T Instantiate<T>(T prefab, Vector3 position, Quaternion rotation) where T : MonoBehaviour
  {
    return this.LocalInstantiate<T>(prefab, position, rotation);
  }

  void IGameEngine.OnEntitySpawned(Entity ent)
  {
    NetworkObject userEntity = (NetworkObject) ent.UserEntity;
    this.AllObjects.Add(ent.RawId, userEntity);
    this.Events.InvokeOnObjectCreated(userEntity);
  }

  void IGameEngine.OnEntityDespawned(Entity ent)
  {
    NetworkObject userEntity = (NetworkObject) ent.UserEntity;
    if (this.AllObjects.TryGetValue(ent.RawId, out NetworkObject _))
      this.Events.InvokeOnObjectDestroyed(userEntity);
    this.RemoveNetworkObject(userEntity);
  }

  unsafe void IGameEngine.PopulateEntityMeta(Entity ent)
  {
    NetworkObject userEntity = (NetworkObject) ent.UserEntity;
    NetworkObjectMeta metaRef = *(NetworkObjectMeta*) this.Engine.GetEntityUserMeta(userEntity.Entity) with
    {
      CreateId = userEntity.IsPrefabObject ? -userEntity.PrefabId : userEntity._RuntimeSceneId,
      ParentId = userEntity.ParentId
    };
    if (userEntity.IsPrefabObject)
    {
      metaRef.PrefabChildIndex = userEntity.PrefabIndex;
      metaRef.PrefabRootId = userEntity.PrefabIndex != -1 ? userEntity.PrefabRoot.RawId : -1;
    }
    if (!userEntity.IsPrefabObject && userEntity._RuntimeSceneId != 0)
      metaRef.LoadIdCounter = userEntity.Scene.LoadCounterId;
    this.SetEntityMetaPosRot(userEntity, ref metaRef);
    this.Engine.SetEntityUserMetaDirty(ent, (int*) &metaRef);
  }

  internal unsafe void SetEntityMetaPosRot(NetworkObject entity, ref NetworkObjectMeta metaRef)
  {
    if (!this.Config.SyncTransformOnSpawn)
      return;
    NetworkObjectMeta networkObjectMeta = metaRef;
    Vector3* data1 = &networkObjectMeta.Position;
    Quaternion* data2 = &networkObjectMeta.Rotation;
    NetickUnityUtils.SetVector3((int*) data1, entity.transform.position, 1000f);
    NetickUnityUtils.SetQuaternion((int*) data2, entity.transform.rotation, 10000f);
    metaRef.Position = *data1;
    metaRef.Rotation = *data2;
  }

  internal unsafe void GetEntityMetaPosRot(
    NetworkObjectMeta* metaRef,
    out Vector3 pos,
    out Quaternion rot)
  {
    if (!this.Config.SyncTransformOnSpawn)
    {
      pos = new Vector3();
      rot = Quaternion.identity;
    }
    else
    {
      Vector3* data1 = &metaRef->Position;
      Quaternion* data2 = &metaRef->Rotation;
      pos = NetickUnityUtils.GetVector3((int*) data1, 1f / 1000f);
      rot = NetickUnityUtils.GetQuaternion((int*) data2, 0.0001f);
    }
  }

  unsafe void IGameEngine.OnEntityMetaChanged1(
    NetickEntityMeta netickMeta,
    byte* metaData,
    int worldIndex)
  {
    NetworkObjectMeta* metaRef = (NetworkObjectMeta*) metaData;
    int id = netickMeta.Id;
    Entity entity1;
    if (this.Engine.Simulation.UnlinkedEntities.TryGetValue(netickMeta.Id, out entity1) && (netickMeta.IsDestroyed || entity1.RawId != netickMeta.Id))
      this.Engine.DestroyEntity(entity1, true, true);
    Entity entity2;
    if (this.Engine.AllEntities.TryGetValue(id, out entity2))
    {
      NetworkObject userEntity = entity2.UserEntity as NetworkObject;
      if (netickMeta.IsDestroyed && !userEntity.IsPrefabChild)
        this.ClientDestroyObject(userEntity);
    }
    Entity entityByWorldIndex = this.Engine.GetEntityByWorldIndex(worldIndex);
    if (entityByWorldIndex != null && entityByWorldIndex.RawId != id && entityByWorldIndex.RawId != -1)
    {
      NetworkObject userEntity = entityByWorldIndex.UserEntity as NetworkObject;
      Entity entity3;
      if (this.Engine.Simulation.UnlinkedEntities.TryGetValue(entityByWorldIndex.RawId, out entity3))
        this.Engine.DestroyEntity(entity3, true, true);
      else if (!userEntity.IsPrefabChild)
        this.ClientDestroyObject(userEntity);
    }
    if (netickMeta.IsDestroyed)
      return;
    int num = metaRef->CreateId < 0 ? 1 : 0;
    int key1 = num != 0 ? -metaRef->CreateId : metaRef->CreateId;
    Vector3 pos;
    Quaternion rot;
    this.GetEntityMetaPosRot(metaRef, out pos, out rot);
    if (num != 0)
    {
      if (metaRef->PrefabChildIndex != -1 || this.Engine.AllEntities.ContainsKey(id))
        return;
      NetworkObject component = this.IdToPrefabPool[key1].Get(pos, rot, this.Config.SyncTransformOnSpawn).GetComponent<NetworkObject>();
      if (component.IsPersistent)
        UnityEngine.Object.DontDestroyOnLoad((UnityEngine.Object) component);
      this.Engine.ClientAddEntity((INetickEntity) component, id, worldIndex, false, netickMeta);
      this.AttachGameObject(component.gameObject);
      component.gameObject.SetActive(false);
    }
    else
    {
      if (this.Engine.AllEntities.ContainsKey(id))
        return;
      NetworkObject entity4 = (NetworkObject) null;
      if (key1 == 0)
      {
        entity4 = this.SandboxNetworkObject;
        this.Engine.ClientAddEntity((INetickEntity) this.SandboxNetworkObject, id, worldIndex, false, new NetickEntityMeta());
      }
      else
      {
        int operationsCounter = ((ConnectionMeta*) this.ConnectedServer.RemoteUserData)->SceneOperationsCounter;
        if (metaRef->LoadIdCounter == operationsCounter)
        {
          entity4 = this.MainScene.NetworkObjects[key1];
        }
        else
        {
          NetworkScene networkScene;
          if (this.NetworkSceneManager.LoadedAdditiveScenes.TryGetValue(metaRef->LoadIdCounter, out networkScene) && networkScene.IsReady)
            entity4 = networkScene.NetworkObjects[key1];
        }
        if ((UnityEngine.Object) entity4 != (UnityEngine.Object) null)
          this.Engine.ClientAddEntity((INetickEntity) entity4, id, worldIndex, false, new NetickEntityMeta());
        else if (!this.Engine.Simulation.UnlinkedEntities.TryGetValue(id, out Entity _))
        {
          Entity entity5 = this.Engine.CreateEntity(netickMeta.StateSizeWords);
          this.Engine.ClientAddUnlinkedEntity(entity5, id, worldIndex);
          ScenePendingObjectKey key2 = new ScenePendingObjectKey()
          {
            SceneObjectId = key1,
            SceneLoadId = metaRef->LoadIdCounter
          };
          entity5.UserId = metaRef->LoadIdCounter;
          entity5.BehsHash = key1;
          this.PendingSceneObjects.Add(key2, entity5);
        }
      }
      if (!((UnityEngine.Object) entity4 != (UnityEngine.Object) null) || !this.Config.SyncTransformOnSpawn)
        return;
      entity4.transform.position = pos;
      entity4.transform.rotation = rot;
    }
  }

  unsafe void IGameEngine.OnEntityMetaChangedPhase2(
    NetickEntityMeta netickMeta,
    byte* metaData,
    int worldIndex)
  {
    NetworkObjectMeta* metaRef = (NetworkObjectMeta*) metaData;
    if (netickMeta.IsDestroyed || metaRef->CreateId >= 0 || metaRef->PrefabChildIndex == -1)
      return;
    NetworkObject child = (this.Engine.AllEntities[metaRef->PrefabRootId].UserEntity as NetworkObject).Children[metaRef->PrefabChildIndex];
    if (!this.Engine.AllEntities.ContainsKey(netickMeta.Id))
      this.Engine.ClientAddEntity((INetickEntity) child, netickMeta.Id, worldIndex, false, netickMeta);
    if (!this.Config.SyncTransformOnSpawn)
      return;
    Vector3 pos;
    Quaternion rot;
    this.GetEntityMetaPosRot(metaRef, out pos, out rot);
    child.transform.position = pos;
    child.transform.rotation = rot;
  }

  unsafe void IGameEngine.OnEntityMetaChangedPhase3(
    NetickEntityMeta netickMeta,
    byte* metaData,
    int worldIndex)
  {
    NetworkObjectMeta* networkObjectMetaPtr = (NetworkObjectMeta*) metaData;
    Entity entity;
    if (netickMeta.IsDestroyed || !this.Engine.AllEntities.TryGetValue(netickMeta.Id, out entity))
      return;
    (entity.UserEntity as NetworkObject).NetworkSetParent(networkObjectMetaPtr->ParentId);
  }

  void IGameEngine.OnUnlinkedEntityDespawned(Entity entity)
  {
    this.PendingSceneObjects.Remove(new ScenePendingObjectKey(entity.BehsHash, entity.UserId));
  }

  private void InitNetworkPrefabPools(List<NetworkObject> prefabs)
  {
    for (int index = 0; index < prefabs.Count; ++index)
    {
      int num = index + 1;
      NetworkObject prefab = prefabs[index];
      NetworkObjectPool networkObjectPool = new NetworkObjectPool(this, prefab.gameObject, 0, false, num);
      this.IdToPrefabPool.Add(num, networkObjectPool);
      this.PrefabToPrefabPool.Add(prefab.gameObject, networkObjectPool);
    }
  }

  private void PreloadNetworkPrefabPools(List<NetworkObject> prefabs)
  {
    foreach (NetworkObjectPool networkObjectPool in this.PrefabToPrefabPool.Values)
      networkObjectPool.Preload(networkObjectPool.PreloadAmount);
  }

  public void InitializePool(
    NetworkObject networkedPrefab,
    int preloadedAmount,
    bool hideInactiveMembers = false)
  {
    this.InitializePool(networkedPrefab.gameObject, preloadedAmount, hideInactiveMembers);
  }

  /// <summary>
  /// Initializes the pool for the specified prefab. After this method has been called for a certain prefab, all instances of that prefab will be recycled and reset when created/destroyed.
  /// <para>Note: this method should only be called on <see cref="M:Netick.Unity.NetworkEventsListener.OnStartup(Netick.Unity.NetworkSandbox)" />, in other words, just after Netick has been started. </para>
  /// </summary>
  /// <param name="networkedPrefab">Prefab to enable pooling for.</param>
  /// <param name="preloadedAmount">How many instances to be preloaded.</param>
  /// <param name="hideInactiveMembers">Pass true to hide inactive pool members.</param>
  public void InitializePool(
    GameObject networkedPrefab,
    int preloadedAmount,
    bool hideInactiveMembers = false)
  {
    networkedPrefab.GetComponent<NetworkObject>();
    NetworkObjectPool networkObjectPool = this.PrefabToPrefabPool[networkedPrefab];
    networkObjectPool.PreloadAmount = preloadedAmount;
    networkObjectPool.UsePool = true;
    networkObjectPool.HideUnspawned = hideInactiveMembers;
    networkObjectPool.Reload();
  }

  public void DestroyPool(NetworkObject networkedPrefab)
  {
    this.PrefabToPrefabPool[networkedPrefab.gameObject].Decommission();
  }

  public void DestroyPool(GameObject networkedPrefab)
  {
    this.PrefabToPrefabPool[networkedPrefab].Decommission();
  }

  internal void DestroyNetworkPools()
  {
    foreach (NetworkObjectPool networkObjectPool in this.IdToPrefabPool.Values)
      networkObjectPool.DestroyEverything();
  }

  /// <summary>Gets a Network Prefab by name.</summary>
  /// <returns>The Network Prefab</returns>
  public GameObject GetPrefab(string name)
  {
    foreach (NetworkObject prefab in this.Config.Prefabs)
    {
      if (prefab.gameObject.name == name)
        return prefab.gameObject;
    }
    return (GameObject) null;
  }

  /// <summary>
  /// <i><b>[Server Only]</b></i> Instantiates a network prefab.
  /// <para>This must only be called on the server, since only the server can instantiate network prefabs.</para>
  /// <para>Note: make sure the prefab has been registered. </para>
  /// <para>Note: the specified input source will be given to every <see cref="T:Netick.Unity.NetworkObject" /> child of this prefab.</para>
  /// </summary>
  internal NetworkObject NetworkInstantiateInternal(
    GameObject prefab,
    Vector3 position,
    Quaternion rotation,
    Netick.NetworkPlayer inputSource = null)
  {
    if (this.Engine.IsClient)
      throw new NotServerException("Only the server can instantiate network prefabs.");
    prefab.SetActive(true);
    NetworkObject component1;
    if (!prefab.TryGetComponent<NetworkObject>(out component1))
      return (NetworkObject) null;
    if (!this.PrefabToPrefabPool.ContainsKey(component1.gameObject))
    {
      NetickLogger.LogError(this.Engine, (object) "The prefab you want to instantiate is not added to the network prefab list! If you are using the default prefab list, go to Netick -> Settings -> Misc -> Prefabs and press Regenerate to load prefabs in the provided path.");
      return (NetworkObject) null;
    }
    NetworkObject component2 = this.PrefabToPrefabPool[component1.gameObject].Get(position, rotation, true).GetComponent<NetworkObject>();
    if (component2.IsPersistent)
      UnityEngine.Object.DontDestroyOnLoad((UnityEngine.Object) component2);
    this.LinkObject(component2.gameObject, inputSource);
    this.AttachGameObject(component2.gameObject);
    return component2;
  }

  private void LinkObject(GameObject obj, Netick.NetworkPlayer inputSource)
  {
    NetworkObject component = obj.GetComponent<NetworkObject>();
    if ((UnityEngine.Object) component != (UnityEngine.Object) null && (component.gameObject.activeInHierarchy || this.Config.IncludeInactiveObjects))
      this.Engine.ServerAddEntity((INetickEntity) component, inputSource, true);
    for (int index = 0; index < obj.transform.childCount; ++index)
      this.LinkObject(obj.transform.GetChild(index).gameObject, inputSource);
  }

  private void InitSandboxObject()
  {
    if (!((UnityEngine.Object) this.SandboxNetworkObject != (UnityEngine.Object) null))
      return;
    this.SandboxNetworkObject._IsPersistent = true;
    this.SandboxNetworkObject._PredictionMode = Relevancy.Everyone;
    this.SandboxNetworkObject._RuntimeSceneId = 0;
    this.SandboxNetworkObject.PrefabId = -1;
    this.SandboxNetworkObject.PrefabIndex = -1;
    this.SandboxNetworkObject.Children = new NetworkObject[0];
    this.SandboxNetworkObject.InitLocally(this, (NetworkScene) null);
    this.Engine.CreateAndLinkEntityLocal((INetickEntity) this.SandboxNetworkObject);
    if (!this.Engine.IsServer)
      return;
    this.Engine.ServerAddEntity((INetickEntity) this.SandboxNetworkObject, (Netick.NetworkPlayer) null, false);
  }

  private void InvokeNetworkAwakeOnSandboxObject()
  {
    for (int index = 0; index < this.SandboxNetworkObject.NetworkedBehaviours.Length; ++index)
    {
      try
      {
        this.SandboxNetworkObject.NetworkedBehaviours[index].NetworkAwake();
      }
      catch (Exception ex)
      {
        NetickLogger.LogError((object) this, (object) ex);
      }
    }
  }

  /// <summary>
  /// <i><b>[Server Only]</b></i> Destroys a network object.
  /// <para>Note: never call this on a child <see cref="T:Netick.Unity.NetworkObject" /> of the original prefab, only the root of the prefab.</para>
  /// </summary>
  /// <param name="obj">The object to destroy.</param>
  /// <returns></returns>
  public void Destroy(NetworkObject obj)
  {
    if (this.Engine.IsClient)
      throw new NotServerException("Only the server can destroy network objects.");
    if (obj.IsPrefabChild && obj.IsPrefabInstance)
      Debug.LogError((object) "You can't destroy a prefab child. You must destroy the prefab root.");
    else
      this.ServerDestroyObject(obj.gameObject);
  }

  internal void ServerDestroyObject(GameObject obj, bool isInstant = false)
  {
    for (int index = 0; index < obj.transform.childCount; ++index)
      this.ServerDestroyObject(obj.transform.GetChild(index).gameObject);
    NetworkObject component;
    if (!obj.TryGetComponent<NetworkObject>(out component) || component.Entity == null)
      return;
    int rawId = component.RawId;
    foreach (NetworkObject child in component.Children)
    {
      bool isPrefabRoot;
      this.GetObjectResetInfo(child, out bool _, out isPrefabRoot);
      this.Engine.DestroyEntity(child.Entity, isInstant, !isPrefabRoot);
    }
    if (!((UnityEngine.Object) component.PrefabRoot == (UnityEngine.Object) null))
      return;
    bool isPrefabRoot1;
    this.GetObjectResetInfo(component, out bool _, out isPrefabRoot1);
    this.Engine.DestroyEntity(component.Entity, isInstant, !isPrefabRoot1);
    if (!((UnityEngine.Object) component.PrefabRoot == (UnityEngine.Object) null) || isPrefabRoot1 || rawId != -1)
      return;
    UnityEngine.Object.Destroy((UnityEngine.Object) component.gameObject);
  }

  internal void ClientDestroyObject(NetworkObject entity)
  {
    int rawId = entity.RawId;
    bool isPrefabRoot;
    this.GetObjectResetInfo(entity, out bool _, out isPrefabRoot);
    this.UnlinkChildren(entity.transform);
    foreach (NetworkObject child in entity.Children)
      this.Engine.DestroyEntity(child.Entity, true, !isPrefabRoot);
    this.Engine.DestroyEntity(entity.Entity, true, !isPrefabRoot);
    if (!((UnityEngine.Object) entity.PrefabRoot == (UnityEngine.Object) null) || isPrefabRoot || rawId != -1)
      return;
    UnityEngine.Object.Destroy((UnityEngine.Object) entity.gameObject);
  }

  private void UnlinkChildren(Transform obj)
  {
    for (int index = obj.childCount - 1; index >= 0; --index)
    {
      Transform child = obj.GetChild(index);
      NetworkObject component;
      if (child.TryGetComponent<NetworkObject>(out component) && component.Entity != null && (UnityEngine.Object) component.PrefabRoot == (UnityEngine.Object) null)
        component.InternalSetParent((NetworkObject) null);
      this.UnlinkChildren(child);
    }
  }

  private void GetObjectResetInfo(
    NetworkObject entity,
    out bool shouldReset,
    out bool isPrefabRoot)
  {
    isPrefabRoot = false;
    bool flag = entity.IsPrefabInstance && this.IdToPrefabPool[entity.PrefabId].UsePool;
    shouldReset = ((entity.IsPrefabInstance ? 1 : ((UnityEngine.Object) entity.PrefabRoot != (UnityEngine.Object) null ? 1 : 0)) & (flag ? 1 : 0)) != 0;
    if (((entity.PrefabId <= -1 ? 0 : ((UnityEngine.Object) entity.PrefabRoot == (UnityEngine.Object) null ? 1 : 0)) & (flag ? 1 : 0)) == 0)
      return;
    isPrefabRoot = true;
  }

  private void RemoveNetworkObject(NetworkObject netObj)
  {
    if ((UnityEngine.Object) netObj == (UnityEngine.Object) this.SandboxNetworkObject)
    {
      this.AllObjects.Remove(netObj.RawId);
      this.InvokeNetworkReset(netObj);
    }
    else
    {
      if (netObj.IsSceneObject)
        netObj.Scene?.NetworkObjects.Remove(netObj._RuntimeSceneId);
      this.AllObjects.Remove(netObj.RawId);
      bool shouldReset;
      bool isPrefabRoot;
      this.GetObjectResetInfo(netObj, out shouldReset, out isPrefabRoot);
      if (shouldReset)
        this.InvokeNetworkReset(netObj);
      if (isPrefabRoot)
      {
        netObj.transform.parent = (Transform) null;
        netObj.InitParentData();
        this.IdToPrefabPool[netObj.PrefabId].Push(netObj.gameObject);
      }
      else
      {
        if (!((UnityEngine.Object) netObj.PrefabRoot == (UnityEngine.Object) null))
          return;
        UnityEngine.Object.Destroy((UnityEngine.Object) netObj.gameObject);
      }
    }
  }

  private void InvokeNetworkReset(NetworkObject obj)
  {
    foreach (NetworkBehaviour networkedBehaviour in obj.NetworkedBehaviours)
    {
      try
      {
        networkedBehaviour.NetworkReset();
      }
      catch (Exception ex)
      {
        NetickLogger.LogError((object) ex);
      }
    }
  }
}
